标签
缓存
字数
3695 字
阅读时间
22 分钟
配置类
java
import com.commnetsoft.core.cache.extend.CommnetFastJsonRedisSerializer;
import com.commnetsoft.core.cache.extend.CommnetRedisCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
/**
* 缓存配置类
*
* @author Brack.zhu
* @date 2020/4/8
*/
@Configuration
public class CacheConfig {
private CommnetFastJsonRedisSerializer commnetFastJsonRedisSerializer=new CommnetFastJsonRedisSerializer();
/**
* Spring Cache Redis配置<br/>
* <ou>
* <li>序列化使用FastJson</li>
* <ou/>
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisCacheManager custCacheManager(RedisConnectionFactory redisConnectionFactory) {
//包装成SerializationPair类型
RedisSerializationContext.SerializationPair serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(commnetFastJsonRedisSerializer);
//redis默认配置文件
//统一设置过期时间
//.entryTtl(Duration.ofDays(1));
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
//设置序列化器
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(serializationPair);
//扩展 RedisCacheManager 生成器创建
CommnetRedisCacheManager.CommnetRedisCacheManagerBuilder builder=CommnetRedisCacheManager.commnetBuilder(redisConnectionFactory).cacheDefaults(redisCacheConfiguration);
return builder.build();
}
/**
* RedisTemplate 序列化使用用FastJson
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置默认的Serialize,包含 valueSerializer
redisTemplate.setDefaultSerializer(commnetFastJsonRedisSerializer);
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
return redisTemplate;
}
/**
* 为fastJson 反序列化创建类白名单,因为fastjson @type自动类型在类没有序列化就直接反序列化是禁止的,
* 所以将SpringCache注解的返回类增加到白名单。<br/>
* 第一次保存和使用缓存对象:序列化 反序列化 操作正常<br/>>
* 服务重启后使用缓存对象:出现autoTypeSupport异常<br/>>
* 该方法解决这个异常问题
* @param ae
*/
public static void fastjsonAddAccept(AnnotatedElement ae){
if(ae instanceof Method){
Method aeMethod=(Method)ae;
Class<?> returnClazz=aeMethod.getReturnType();
CommnetFastJsonRedisSerializer.getDefaultRedisConfig().addAccept(returnClazz.getName());
}
}
public CommnetFastJsonRedisSerializer getCommnetFastJsonRedisSerializer() {
return commnetFastJsonRedisSerializer;
}
}fastjson序列化
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.util.IOUtils;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
/**
* Redis FastJson对象序列化处理器<br/>
* 修改点:将defaultRedisConfig对象开放外部访问方法
* 参照实现类 {@link com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer}
* @author Brack.zhu
* @date 2020/5/13
*/
public class CommnetFastJsonRedisSerializer implements RedisSerializer<Object> {
private final static ParserConfig defaultRedisConfig = new ParserConfig();
static {
defaultRedisConfig.setAutoTypeSupport(true);
}
@Override
public byte[] serialize(Object object) throws SerializationException {
if (object == null) {
return new byte[0];
}
try {
return JSON.toJSONBytes(object, SerializerFeature.WriteClassName);
} catch (Exception ex) {
throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);
}
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
try {
return JSON.parseObject(new String(bytes, IOUtils.UTF8), Object.class, defaultRedisConfig);
} catch (Exception ex) {
throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
}
}
public static ParserConfig getDefaultRedisConfig() {
return defaultRedisConfig;
}
}自定义缓存(支持自定义有效期)
java
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.time.Duration;
/**
* 自定义RedisCache<br/>
* 支持缓存自定义有效期
* @author Brack.zhu
* @date 2020/4/8
*/
public class CommnetRedisCache extends RedisCache {
private final String name;
private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration cacheConfig;
private final ConversionService conversionService;
/**
* Create new {@link RedisCache}.
*
* @param name must not be {@literal null}.
* @param cacheWriter must not be {@literal null}.
* @param cacheConfig must not be {@literal null}.
*/
protected CommnetRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
super(name, cacheWriter, cacheConfig);
this.name = name;
this.cacheWriter = cacheWriter;
this.cacheConfig = cacheConfig;
this.conversionService = cacheConfig.getConversionService();
}
private byte[] createAndConvertCacheKey(Object key) {
return serializeCacheKey(createCacheKey(key));
}
@Override
public void put(Object key, Object value) {
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
}
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheTTL(key));
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
return get(key);
}
byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
cacheTTL(key));
if (result == null) {
return null;
}
return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result)));
}
/**
* 扩展---根据key获取对应缓存 超时时间
* @param key
* @return
*/
private Duration cacheTTL(Object key){
//超时时间改造
Long duration=CommnetRedisCacheManager.getCacheDuration();
if(null!=duration){
return Duration.ofMillis(duration);
}else{
return cacheConfig.getTtl();
}
}
}自定义RedisCacheManager
java
import com.commnetsoft.commons.utils.ThreadLocalUtils;
import org.springframework.data.redis.cache.*;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* 自定义RedisCacheManager
* @author Brack.zhu
* @date 2020/4/8
*/
public class CommnetRedisCacheManager extends RedisCacheManager {
private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration defaultCacheConfig;
public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
}
public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
}
public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
}
public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
}
public CommnetRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
}
/*---------------------------扩展增加 start-----------------------------------*/
/**
* 缓存超时时间 ThreadLocal key
*/
public static final String CACHE_DURATION_KEY="Cache_Duration_Key";
/**
* 增加缓存超时时间 线程局部变量
* @param duration
*/
public static void addCacheDuration(long duration) {
ThreadLocalUtils.set(CACHE_DURATION_KEY,duration);
}
/**
* 清除缓存超时时间 线程局部变量
*/
public static void clearCacheDuration() {
ThreadLocalUtils.del(CACHE_DURATION_KEY);
}
/**
* 获取缓存超时时间 线程局部变量
* @return
*/
public static Long getCacheDuration(){
return ThreadLocalUtils.get(CACHE_DURATION_KEY);
}
/*---------------------------扩展增加 end-----------------------------------*/
@Override
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
return new CommnetRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
/**
* Entry point for builder style {@link RedisCacheManager} configuration.
*
* @param connectionFactory must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
*/
public static CommnetRedisCacheManagerBuilder commnetBuilder(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
return CommnetRedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);
}
/**
* Entry point for builder style {@link RedisCacheManager} configuration.
*
* @param cacheWriter must not be {@literal null}.
* @return new {@link RedisCacheManagerBuilder}.
*/
public static CommnetRedisCacheManagerBuilder commnetBuilder(RedisCacheWriter cacheWriter) {
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
return CommnetRedisCacheManagerBuilder.fromCacheWriter(cacheWriter);
}
/**
* Configurator for creating {@link RedisCacheManager}.
*
* @author Christoph Strobl
* @author Mark Strobl
* @author Kezhu Wang
* @since 2.0
*/
public static class CommnetRedisCacheManagerBuilder {
private final RedisCacheWriter cacheWriter;
private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
private boolean enableTransactions;
boolean allowInFlightCacheCreation = true;
private CommnetRedisCacheManagerBuilder(RedisCacheWriter cacheWriter) {
this.cacheWriter = cacheWriter;
}
/**
* Entry point for builder style {@link RedisCacheManager} configuration.
*
* @param connectionFactory must not be {@literal null}.
* @return new {@link RedisCacheManager.RedisCacheManagerBuilder}.
*/
public static CommnetRedisCacheManagerBuilder fromConnectionFactory(RedisConnectionFactory connectionFactory) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
return commnetBuilder(new CommnetRedisCacheWriter(connectionFactory));
}
/**
* Entry point for builder style {@link RedisCacheManager} configuration.
*
* @param cacheWriter must not be {@literal null}.
* @return new {@link RedisCacheManager.RedisCacheManagerBuilder}.
*/
public static CommnetRedisCacheManagerBuilder fromCacheWriter(RedisCacheWriter cacheWriter) {
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
return new CommnetRedisCacheManagerBuilder(cacheWriter);
}
/**
* Define a default {@link RedisCacheConfiguration} applied to dynamically created {@link RedisCache}s.
*
* @param defaultCacheConfiguration must not be {@literal null}.
* @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
*/
public CommnetRedisCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {
Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
this.defaultCacheConfiguration = defaultCacheConfiguration;
return this;
}
/**
* Enable {@link RedisCache}s to synchronize cache put/evict operations with ongoing Spring-managed transactions.
*
* @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
*/
public CommnetRedisCacheManagerBuilder transactionAware() {
this.enableTransactions = true;
return this;
}
/**
* Append a {@link Set} of cache names to be pre initialized with current {@link RedisCacheConfiguration}.
* <strong>NOTE:</strong> This calls depends on {@link #cacheDefaults(RedisCacheConfiguration)} using whatever
* default {@link RedisCacheConfiguration} is present at the time of invoking this method.
*
* @param cacheNames must not be {@literal null}.
* @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
*/
public CommnetRedisCacheManagerBuilder initialCacheNames(Set<String> cacheNames) {
Assert.notNull(cacheNames, "CacheNames must not be null!");
Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
cacheNames.forEach(it -> cacheConfigMap.put(it, defaultCacheConfiguration));
return withInitialCacheConfigurations(cacheConfigMap);
}
/**
* Append a {@link Map} of cache name/{@link RedisCacheConfiguration} pairs to be pre initialized.
*
* @param cacheConfigurations must not be {@literal null}.
* @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
*/
public CommnetRedisCacheManagerBuilder withInitialCacheConfigurations(
Map<String, RedisCacheConfiguration> cacheConfigurations) {
Assert.notNull(cacheConfigurations, "CacheConfigurations must not be null!");
cacheConfigurations.forEach((cacheName, configuration) -> Assert.notNull(configuration,
String.format("RedisCacheConfiguration for cache %s must not be null!", cacheName)));
this.initialCaches.putAll(cacheConfigurations);
return this;
}
/**
* Disable in-flight {@link org.springframework.cache.Cache} creation for unconfigured caches.
* <p/>
* {@link RedisCacheManager#getMissingCache(String)} returns {@literal null} for any unconfigured
* {@link org.springframework.cache.Cache} instead of a new {@link RedisCache} instance. This allows eg.
* {@link org.springframework.cache.support.CompositeCacheManager} to chime in.
*
* @return this {@link RedisCacheManager.RedisCacheManagerBuilder}.
* @since 2.0.4
*/
public CommnetRedisCacheManagerBuilder disableCreateOnMissingCache() {
this.allowInFlightCacheCreation = false;
return this;
}
/**
* Create new instance of {@link RedisCacheManager} with configuration options applied.
*
* @return new instance of {@link RedisCacheManager}.
*/
public RedisCacheManager build() {
CommnetRedisCacheManager cm = new CommnetRedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches, allowInFlightCacheCreation);
cm.setTransactionAware(enableTransactions);
return cm;
}
}
}java
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* 代码完全依照{org.springframework.data.redis.cache.DefaultRedisCacheWriter}
* @author Brack.zhu
* @date 2020/4/8
*/
public class CommnetRedisCacheWriter implements RedisCacheWriter {
private final RedisConnectionFactory connectionFactory;
private final Duration sleepTime;
/**
* @param connectionFactory must not be {@literal null}.
*/
CommnetRedisCacheWriter(RedisConnectionFactory connectionFactory) {
this(connectionFactory, Duration.ZERO);
}
/**
* @param connectionFactory must not be {@literal null}.
* @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO}
* to disable locking.
*/
CommnetRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
Assert.notNull(sleepTime, "SleepTime must not be null!");
this.connectionFactory = connectionFactory;
this.sleepTime = sleepTime;
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.cache.RedisCacheWriter#put(java.lang.String, byte[], byte[], java.time.Duration)
*/
@Override
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
execute(name, connection -> {
if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.upsert());
} else {
connection.set(key, value);
}
return "OK";
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.cache.RedisCacheWriter#get(java.lang.String, byte[])
*/
@Override
public byte[] get(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
return execute(name, connection -> connection.get(key));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.cache.RedisCacheWriter#putIfAbsent(java.lang.String, byte[], byte[], java.time.Duration)
*/
@Override
public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
return execute(name, connection -> {
if (isLockingCacheWriter()) {
doLock(name, connection);
}
try {
if (connection.setNX(key, value)) {
if (shouldExpireWithin(ttl)) {
connection.pExpire(key, ttl.toMillis());
}
return null;
}
return connection.get(key);
} finally {
if (isLockingCacheWriter()) {
doUnlock(name, connection);
}
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.cache.RedisCacheWriter#remove(java.lang.String, byte[])
*/
@Override
public void remove(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
execute(name, connection -> connection.del(key));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.cache.RedisCacheWriter#clean(java.lang.String, byte[])
*/
@Override
public void clean(String name, byte[] pattern) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(pattern, "Pattern must not be null!");
execute(name, connection -> {
boolean wasLocked = false;
try {
if (isLockingCacheWriter()) {
doLock(name, connection);
wasLocked = true;
}
byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
.toArray(new byte[0][]);
if (keys.length > 0) {
connection.del(keys);
}
} finally {
if (wasLocked && isLockingCacheWriter()) {
doUnlock(name, connection);
}
}
return "OK";
});
}
/**
* Explicitly set a write lock on a cache.
*
* @param name the name of the cache to lock.
*/
void lock(String name) {
execute(name, connection -> doLock(name, connection));
}
/**
* Explicitly remove a write lock from a cache.
*
* @param name the name of the cache to unlock.
*/
void unlock(String name) {
executeLockFree(connection -> doUnlock(name, connection));
}
private Boolean doLock(String name, RedisConnection connection) {
return connection.setNX(createCacheLockKey(name), new byte[0]);
}
private Long doUnlock(String name, RedisConnection connection) {
return connection.del(createCacheLockKey(name));
}
boolean doCheckLock(String name, RedisConnection connection) {
return connection.exists(createCacheLockKey(name));
}
/**
* @return {@literal true} if {@link RedisCacheWriter} uses locks.
*/
private boolean isLockingCacheWriter() {
return !sleepTime.isZero() && !sleepTime.isNegative();
}
private <T> T execute(String name, Function<RedisConnection, T> callback) {
RedisConnection connection = connectionFactory.getConnection();
try {
checkAndPotentiallyWaitUntilUnlocked(name, connection);
return callback.apply(connection);
} finally {
connection.close();
}
}
private void executeLockFree(Consumer<RedisConnection> callback) {
RedisConnection connection = connectionFactory.getConnection();
try {
callback.accept(connection);
} finally {
connection.close();
}
}
private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
if (!isLockingCacheWriter()) {
return;
}
try {
while (doCheckLock(name, connection)) {
Thread.sleep(sleepTime.toMillis());
}
} catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other participants to react.
Thread.currentThread().interrupt();
throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name),
ex);
}
}
private static boolean shouldExpireWithin(@Nullable Duration ttl) {
return ttl != null && !ttl.isZero() && !ttl.isNegative();
}
private static byte[] createCacheLockKey(String name) {
return (name + "~lock").getBytes(StandardCharsets.UTF_8);
}
}自定义缓存配置
java
import com.commnetsoft.core.cache.extend.CommnetCacheInterceptor;
import com.commnetsoft.core.cache.extend.annotation.CommnetSpringCacheAnnotationParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.annotation.ProxyCachingConfiguration;
import org.springframework.cache.config.CacheManagementConfigUtils;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
/**
* 自定义缓存配置----只支持Redis
* @author Brack.zhu
* @date 2020/3/25
*/
@EnableCaching
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
//@Primary
public class CommnetProxyCachingConfiguration extends ProxyCachingConfiguration {
@Value("#{coreConfig.applicationName}")
private String applicationName;
@Override
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
@Override
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor() {
//自定义缓存拦截器
CacheInterceptor interceptor = new CommnetCacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource());
return interceptor;
}
@Override
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
//自定义缓存注解解析器;支持cacheNames\value 自动扩展模块名称。格式:{模块名}_注解value
CommnetSpringCacheAnnotationParser commnetSpringCacheAnnotationParser=new CommnetSpringCacheAnnotationParser(applicationName);
return new AnnotationCacheOperationSource(commnetSpringCacheAnnotationParser);
}
}缓存拦截器(拦截注解配置超时)
java
import java.lang.annotation.*;
/**
* 缓存超时 注解<br/>
* 需要配合Spring cache 注解使用
* @author Brack.zhu
* @date 2020/4/8
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheDuration {
/**
* 最大存活时间,使用常量参数;默认:-1(永久),单位毫秒
* @return
*/
long value() default -1;
/**
* 最大存活时间,使用变量参数,使用Spring的表达式,SpEL 如:#{systemProperties.myProp}<br/>
* 表达式返回值类型long,单位毫秒,
*
* @return
*/
String var() default "";
}
import com.commnetsoft.commons.utils.StringUtils;
import com.commnetsoft.core.cache.CacheDuration;
import com.commnetsoft.core.utils.SpElUtil;
import com.commnetsoft.core.utils.SpringContextUtil;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheInterceptor;
/**
* 自定义缓存拦截器
*
* @author Brack.zhu
* @date 2020/3/25
*/
@SuppressWarnings("serial")
public class CommnetCacheInterceptor extends CacheInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//扩展---支持缓存单独设置存活时间
boolean clear = false;
try {
if (isSupport()) {
//设置单个缓存超时时间
CacheDuration cacheDuration = invocation.getMethod().getAnnotation(CacheDuration.class);
if (null != cacheDuration) {
long duration = 0;
if (cacheDuration.value() > 0) {
//指定常量值
duration = cacheDuration.value();
} else if (StringUtils.isNotBlank(cacheDuration.var())) {
//使用参数变量值
//使用SPEL进行var的解析
duration=SpElUtil.evaluate(cacheDuration.var());
}
if (duration > 0) {
clear = true;
CommnetRedisCacheManager.addCacheDuration(duration);
}
}
}
return super.invoke(invocation);
} finally {
if (clear) {
//清除单个缓存超时时间
CommnetRedisCacheManager.clearCacheDuration();
}
}
}
private Boolean isSupportCacheType = null;
/**
* 是否是支持的缓存类型
*
* @return false不支持
*/
public boolean isSupport() {
if (null == isSupportCacheType) {
CacheManager cacheManager = SpringContextUtil.getBean(CacheManager.class);
if (cacheManager instanceof CommnetRedisCacheManager) {
isSupportCacheType = true;
} else {
isSupportCacheType = false;
}
}
return isSupportCacheType;
}
}缓存注解解析器扩展
java
import com.commnetsoft.core.CoreConstant;
import org.springframework.cache.annotation.*;
import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Spring缓存注解解析器扩展<br>
* 修改点:1:支持cacheNames\value 自动扩展模块名称。格式:{模块名}_注解value <br/>
* 2:自动将SpringCache注解返回类增加到FastJson序列化白名单中
* 参考实现 {@link org.springframework.cache.annotation.SpringCacheAnnotationParser}
* @author Brack.zhu
* @date 2020/5/12
*/
public class CommnetSpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
private String applicationName;
public CommnetSpringCacheAnnotationParser(String applicationName){
this.applicationName=applicationName;
}
private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);
/**
* 扩展---cachename 间隔符
*/
public static final String CacheNameMark= CoreConstant.Cache.REDIS_KEY_MARK;
static {
CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
}
@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig = new CommnetSpringCacheAnnotationParser.DefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}
@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig = new CommnetSpringCacheAnnotationParser.DefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(CommnetSpringCacheAnnotationParser.DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
if (ops != null && ops.size() > 1) {
// More than one operation found -> local declarations override interface-declared ones...
Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
if (localOps != null) {
return localOps;
}
}
return ops;
}
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(
CommnetSpringCacheAnnotationParser.DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
Collection<? extends Annotation> anns = (localOnly ?
AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
if (anns.isEmpty()) {
return null;
}
final Collection<CacheOperation> ops = new ArrayList<>(1);
anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
anns.stream().filter(ann -> ann instanceof CachePut).forEach(
ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
anns.stream().filter(ann -> ann instanceof Caching).forEach(
ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
return ops;
}
private CacheableOperation parseCacheableAnnotation(
AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, Cacheable cacheable) {
//--扩展
fastjsonAccept(ae);
CacheableOperation.Builder builder = new CacheableOperation.Builder();
builder.setName(ae.toString());
//--扩展
builder.setCacheNames(extendCacheName(cacheable.cacheNames()));
builder.setCondition(cacheable.condition());
builder.setUnless(cacheable.unless());
builder.setKey(cacheable.key());
builder.setKeyGenerator(cacheable.keyGenerator());
builder.setCacheManager(cacheable.cacheManager());
builder.setCacheResolver(cacheable.cacheResolver());
builder.setSync(cacheable.sync());
defaultConfig.applyDefault(builder);
CacheableOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
private CacheEvictOperation parseEvictAnnotation(
AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
//--扩展
fastjsonAccept(ae);
CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
builder.setName(ae.toString());
//--扩展
builder.setCacheNames(extendCacheName(cacheEvict.cacheNames()));
builder.setCondition(cacheEvict.condition());
builder.setKey(cacheEvict.key());
builder.setKeyGenerator(cacheEvict.keyGenerator());
builder.setCacheManager(cacheEvict.cacheManager());
builder.setCacheResolver(cacheEvict.cacheResolver());
builder.setCacheWide(cacheEvict.allEntries());
builder.setBeforeInvocation(cacheEvict.beforeInvocation());
defaultConfig.applyDefault(builder);
CacheEvictOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
private CacheOperation parsePutAnnotation(
AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, CachePut cachePut) {
//--扩展
fastjsonAccept(ae);
CachePutOperation.Builder builder = new CachePutOperation.Builder();
builder.setName(ae.toString());
//--扩展
builder.setCacheNames(extendCacheName(cachePut.cacheNames()));
builder.setCondition(cachePut.condition());
builder.setUnless(cachePut.unless());
builder.setKey(cachePut.key());
builder.setKeyGenerator(cachePut.keyGenerator());
builder.setCacheManager(cachePut.cacheManager());
builder.setCacheResolver(cachePut.cacheResolver());
defaultConfig.applyDefault(builder);
CachePutOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
private void parseCachingAnnotation(
AnnotatedElement ae, CommnetSpringCacheAnnotationParser.DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) {
Cacheable[] cacheables = caching.cacheable();
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
}
CacheEvict[] cacheEvicts = caching.evict();
for (CacheEvict cacheEvict : cacheEvicts) {
ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
}
CachePut[] cachePuts = caching.put();
for (CachePut cachePut : cachePuts) {
ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
}
}
/**
* Validates the specified {@link CacheOperation}.
* <p>Throws an {@link IllegalStateException} if the state of the operation is
* invalid. As there might be multiple sources for default values, this ensure
* that the operation is in a proper state before being returned.
* @param ae the annotated element of the cache operation
* @param operation the {@link CacheOperation} to validate
*/
private void validateCacheOperation(AnnotatedElement ae, CacheOperation operation) {
if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache annotation configuration on '" +
ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
throw new IllegalStateException("Invalid cache annotation configuration on '" +
ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
"These attributes are mutually exclusive: the cache manager is used to configure a" +
"default cache resolver if none is set. If a cache resolver is set, the cache manager" +
"won't be used.");
}
}
@Override
public boolean equals(Object other) {
return (this == other || other instanceof SpringCacheAnnotationParser);
}
@Override
public int hashCode() {
return SpringCacheAnnotationParser.class.hashCode();
}
/**
* 扩展---cachename缓存加上模块前缀
* @param baseNames
* @return
*/
public String[] extendCacheName(String... baseNames){
String[] extendBaseNames=new String[baseNames.length];
for(int i=0;i<baseNames.length;i++){
String baseName=baseNames[i];
extendBaseNames[i]=applicationName+CacheNameMark+baseName;
}
return extendBaseNames;
}
/**
* 扩展---为fastJson 反序列化创建类白名单
* @param ae
*/
public void fastjsonAccept(AnnotatedElement ae){
com.commnetsoft.core.cache.CacheConfig.fastjsonAddAccept(ae);
}
/**
* Provides default settings for a given set of cache operations.
*/
private static class DefaultCacheConfig {
private final Class<?> target;
@Nullable
private String[] cacheNames;
@Nullable
private String keyGenerator;
@Nullable
private String cacheManager;
@Nullable
private String cacheResolver;
private boolean initialized = false;
public DefaultCacheConfig(Class<?> target) {
this.target = target;
}
/**
* Apply the defaults to the specified {@link CacheOperation.Builder}.
* @param builder the operation builder to update
*/
public void applyDefault(CacheOperation.Builder builder) {
if (!this.initialized) {
CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class);
if (annotation != null) {
this.cacheNames = annotation.cacheNames();
this.keyGenerator = annotation.keyGenerator();
this.cacheManager = annotation.cacheManager();
this.cacheResolver = annotation.cacheResolver();
}
this.initialized = true;
}
if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {
builder.setCacheNames(this.cacheNames);
}
if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&
StringUtils.hasText(this.keyGenerator)) {
builder.setKeyGenerator(this.keyGenerator);
}
if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {
// One of these is set so we should not inherit anything
}
else if (StringUtils.hasText(this.cacheResolver)) {
builder.setCacheResolver(this.cacheResolver);
}
else if (StringUtils.hasText(this.cacheManager)) {
builder.setCacheManager(this.cacheManager);
}
}
}
}二、Guava实现缓存
参考guava